package yagwl.client.widget;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import yagwl.client.model.ItemDTO;
import yagwl.client.model.ItemListCache;
import yagwl.client.resource.Resources;
import yagwl.client.widget.internal.AbstractPager;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Widget;

/**
 * Extends AbstractPager and displays objects in a table.
 * <p>
 * <b>CSS styles</b><br>
 * .ya-dataTable - entire table, which is a FlexTable<br>
 * .ya-dataTable-header - column header cells<br>
 * .ya-dataTable-action - row-level action widgets (edit, delete)
 */
public class PagedFlexTable extends AbstractPager {
  private static final Logger log = Logger.getLogger(PagedFlexTable.class
      .getName());
  // table variables
  private final FlexTable dataFT = new FlexTable();
  private List<TableColumn> columns = new ArrayList<TableColumn>();
  // table action variables
  private String rowCreateHistoryToken;
  private String rowDetailHistoryToken;
  private AbstractItemDetail rowDetailForm;
  private RowActionClickHandler rowCreateClickHandler;
  // row action variables
  private List<RowAction> rowActions = new ArrayList<RowAction>();
  private RowActionClickHandler rowPostEditClickHandler;
  private RowActionCallback rowActionCallback;
  // multi-select variables
  private boolean enableMultiSelect;
  private MultiSelectValueChangeHandler msValueChangeHandler;
  private MultiSelectHeaderClickHandler mshClickHandler;
  // internal variables
  private boolean rowActionInProgress;
  private CheckBox multiSelectHeaderCB;

  public PagedFlexTable(ItemListCache listCache) {
    super(listCache);
  }

  /**
   * Adds a table columns
   */
  public void addColumn(int dataColIdx, String name) {
    TableColumn c = new TableColumn();
    c.setHeader(name);
    columns.add(dataColIdx, c);
  }

  /**
   * Adds a table columns
   */
  public void addColumn(int dataColIdx, String name, String width) {
    TableColumn c = new TableColumn();
    c.setHeader(name);
    c.setWidth(width);
    columns.add(dataColIdx, c);
  }

  /**
   * Adds a custom row action
   */
  public void addCustomRowAction(String icon, String label) {
    rowActions.add(new RowAction(Action.Custom, icon, label));
  }

  /**
   * Enables row create. Displays a create button, upon clicking which navigates
   * to view represented by its history token.
   */
  public void enableRowCreate(String historyToken, String createRowLabel) {
    rowCreateHistoryToken = historyToken;
    rowCreateClickHandler = new RowActionClickHandler(Action.PreCreate);
    addButton(createRowLabel, rowCreateClickHandler);
  }

  /**
   * Enables row edit; an edit icon displayed for each row
   */
  public void enableRowEdit() {
    rowActions.add(new RowAction(Action.PreEdit));
    rowPostEditClickHandler = new RowActionClickHandler(Action.PostEdit);
  }

  /**
   * Enables view and/or edit of row detail; a detail icon is displayed for each
   * row. Navigates to a view represented by its history token. Id of the item
   * to be viewed.edited is appended to the history token
   */
  public void enableRowDetail(String historyToken) {
    rowDetailHistoryToken = historyToken;
    rowActions.add(new RowAction(Action.Detail));
  }

  /**
   * Enables view and/or edit of row detail; a detail icon is displayed for each
   * row. Navigates to a view represented by its history token. Id of the item
   * to be viewed.edited is appended to the history token. The id is also set in
   * the FormPanel (if using one).
   */
  public void enableRowDetail(String historyToken, AbstractItemDetail form) {
    rowDetailHistoryToken = historyToken;
    rowDetailForm = form;
    rowActions.add(new RowAction(Action.Detail));
  }

  /**
   * Enables row delete; a delete icon displayed for each row; upon delete
   * action deleteObject() method of the cache manager will be called. Upon
   * successful deletion, should raise ItemListEvent to refresh the table.
   */
  public void enableRowDelete() {
    rowActions.add(new RowAction(Action.Delete));
  }

  /**
   * Enables row select; a select icon displayed for each row; used in LOV
   * popups
   */
  public void enableRowSelect() {
    rowActions.add(new RowAction(Action.Select));
  }

  /**
   * Enables multi-select of table rows
   */
  public void enableMultiSelect() {
    this.enableMultiSelect = true;
    mshClickHandler = new MultiSelectHeaderClickHandler();
    msValueChangeHandler = new MultiSelectValueChangeHandler();
  }

  private class RowActionClickHandler implements ClickHandler {
    private Action action;

    private RowActionClickHandler(Action action) {
      this.action = action;
    }

    @Override
    public void onClick(ClickEvent event) {
      if (action == Action.PreCreate) {
        if (rowDetailForm != null) {
          rowDetailForm.setListCache(getListCache());
        }
        History.newItem(rowCreateHistoryToken, true);
        return;
      }
      int clickedTblRowIdx = getClickedTblRowIdx(event,
          getTblActionColIdx(action));
      int clickedDataRowIdx = getDataRowIdx(clickedTblRowIdx);
      log.info("RowActionClickHandler.onClick: Action=" + action + " DataRow="
          + clickedDataRowIdx);
      switch (action) {
      case Delete:
        if (rowActionInProgress)
          return;
        rowActionInProgress = true;
        dataFT.setWidget(clickedTblRowIdx, getTblActionColIdx(action),
            new Image(Resources.I.getInlineBusy()));
        deleteListItem(getObjIdx(clickedDataRowIdx));
        break;
      case PreEdit:
        rowActionCallback.action(Action.PreEdit, clickedDataRowIdx,
            getObjIdx(clickedDataRowIdx));
        Image saveI = new Image(Resources.I.getInlineSave());
        saveI.setTitle("Save Changes");
        saveI.addStyleName("ya-dataTable-action");
        dataFT.setWidget(clickedTblRowIdx, getTblActionColIdx(action), saveI);
        saveI.addClickHandler(rowPostEditClickHandler);
        break;
      case PostEdit:
        dataFT.setWidget(clickedTblRowIdx, getTblActionColIdx(action),
            new Image(Resources.I.getInlineBusy()));
        rowActionCallback.action(Action.PostEdit, clickedDataRowIdx,
            getObjIdx(clickedDataRowIdx));
        break;
      case Detail:
        ItemDTO item = getListItem(getObjIdx(clickedDataRowIdx));
        if (rowDetailForm != null)
          rowDetailForm.setItemDTO(item);
        History.newItem(rowDetailHistoryToken + "/" + item.getId(), true);
        break;
      case Select:
        log.info("RowActionClickHandler");
      case Custom:
        rowActionCallback.action(action, clickedDataRowIdx,
            getObjIdx(clickedDataRowIdx));
        break;
      }
    }
  }

  private class MultiSelectHeaderClickHandler implements ClickHandler {
    @Override
    public void onClick(ClickEvent event) {
      CheckBox cbh = (CheckBox) event.getSource();
      for (int tblRowIdx = getTblColHdrRows(); tblRowIdx < dataFT.getRowCount(); tblRowIdx++) {
        CheckBox cb = (CheckBox) dataFT.getWidget(tblRowIdx, 0);
        cb.setValue(cbh.getValue(), true);
      }
    }
  }

  private class MultiSelectValueChangeHandler implements
      ValueChangeHandler<Boolean> {

    @Override
    public void onValueChange(ValueChangeEvent<Boolean> event) {
      CheckBox cbh = (CheckBox) event.getSource();
      for (int tblRowIdx = getTblColHdrRows(); tblRowIdx < dataFT.getRowCount(); tblRowIdx++) {
        CheckBox cb = (CheckBox) dataFT.getWidget(tblRowIdx, 0);
        if (cb == cbh) {
          boolean selected = cb.getValue().equals(true);
          int selectedObjIdx = getObjIdx(getDataRowIdx(tblRowIdx));
          ItemDTO item = getListItem(selectedObjIdx);
          item.setSelected(selected);
          break;
        }
      }
    }
  }

  /**
   * Must be implemented and passed to the DataTable; at a minimum, row populate
   * must be implemented to render rows of the tables.
   */
  public interface RowActionCallback {
    public void action(Action action, int dataRowIdx, int objIdx);
  }

  /**
   * Sets the RowActioncallback
   */
  public void setRowActionCallback(RowActionCallback rowActionCallback) {
    this.rowActionCallback = rowActionCallback;
  }

  /**
   * Renders the widget. Must be called for the widget to be rendered. Must be
   * called after addColumn, setRowPopulateCallback and setActionBar.
   */
  public void renderUI() {
    log.info("renderUI: Entering");
    super.renderUI();
    if (columns != null)
      renderColumnHeaders();
    else
      log.severe("renderUI: Columns not set!");
    dataFT.addStyleName("ya-dataTable");
    // dataFT.setBorderWidth(1);
  }

  protected Widget getDataPanel() {
    return dataFT;
  }

  private void renderColumnHeaders() {
    // render multi-select checkbox column header
    for (int tableCol = 0; tableCol < getTblRowHdrCols(); tableCol++) {
      dataFT.getCellFormatter()
          .addStyleName(0, tableCol, "ya-dataTable-header");
      if (enableMultiSelect) {
        multiSelectHeaderCB = new CheckBox();
        dataFT.setWidget(0, 0, multiSelectHeaderCB);
        multiSelectHeaderCB.addClickHandler(mshClickHandler);
      }
    }

    // render data column headers
    // boolean hasSortableColumn = false;
    for (int dataColIdx = 0; dataColIdx < getDataCols(); dataColIdx++) {
      TableColumn column = columns.get(dataColIdx);
      if (column.getHeader() != null) {
        dataFT.setText(0, getTblColIdx(dataColIdx), column.getHeader());
      }
      dataFT.getCellFormatter().addStyleName(0, getTblColIdx(dataColIdx),
          "ya-dataTable-header");
      // if (column.isSortable()) {
      // dataFT.getCellFormatter().addStyleName(0,
      // getTblColIdx(dataColIdx),
      // "ya-dataTable-header-sortable");
      // hasSortableColumn = true;
      // }

      dataFT.getCellFormatter().setVerticalAlignment(0,
          getTblColIdx(dataColIdx), HasVerticalAlignment.ALIGN_MIDDLE);
      if (column.getWidth() != null)
        dataFT.getCellFormatter().setWidth(0, getTblColIdx(dataColIdx),
            column.getWidth());
    }

    // if any column is sortable, add a clickhandler
    // if (hasSortableColumn) {
    // dataFT.addClickHandler(new ColumnSortClickHandler());
    // }

    for (RowAction ra : rowActions)
      renderActionHeaderCell(ra);
  }

  private void renderActionHeaderCell(RowAction ra) {
    int tblColIdx = getTblActionColIdx(ra.action);
    dataFT.getCellFormatter().addStyleName(0, tblColIdx, "ya-dataTable-header");
    dataFT.getCellFormatter().setWidth(0, tblColIdx, "16px");
  }

  protected void populateDataPanel() {
    rowActionInProgress = false;
    int dataRowIdx = 0;
    for (; dataRowIdx < getPageSize() && getObjIdx(dataRowIdx) < getListSize(); dataRowIdx++) {

      // populate multi-select checkbox cell
      if (enableMultiSelect) {
        CheckBox cb = new CheckBox();
        ItemDTO item = getListItem(getObjIdx(dataRowIdx));
        cb.setValue(item.isSelected());
        dataFT.setWidget(getTblRowIdx(dataRowIdx), 0, cb);
        cb.addValueChangeHandler(msValueChangeHandler);
        // uncheck the header checkbox
        multiSelectHeaderCB.setValue(false);
      }

      // populate data cells
      if (rowActionCallback == null)
        log.severe("populateDataPanel: RowActionCallback not set!");
      else
        rowActionCallback.action(Action.Populate, dataRowIdx,
            getObjIdx(dataRowIdx));

      for (RowAction ra : rowActions)
        populateActionCell(dataRowIdx, ra);
    }

    // clear out the empty rows at the bottom, if any
    // also clear out bottom rows when display range reduced
    for (int tblRowIdx = dataFT.getRowCount() - 1; tblRowIdx >= getTblRowIdx(dataRowIdx); tblRowIdx--) {
      dataFT.removeRow(tblRowIdx);
    }
  }

  private void populateActionCell(int dataRowIdx, RowAction ra) {
    int tblColIdx = getTblActionColIdx(ra.action);
    Image icon = ra.imageS != null ? new Image(ra.imageS)
        : new Image(ra.imageR);
    icon.setTitle(ra.label);
    icon.addStyleName("ya-dataTable-action");
    dataFT.setWidget(getTblRowIdx(dataRowIdx), tblColIdx, icon);
    icon.addClickHandler(ra.handler);
    dataFT.getCellFormatter().setHorizontalAlignment(getTblRowIdx(dataRowIdx),
        tblColIdx, HasHorizontalAlignment.ALIGN_CENTER);
  }

  /**
   * Sets text in a table cell, called from row populate callback
   * 
   * @param dataRowIdx
   *          row index excluding header rows
   * @param dataColIdx
   *          column index excluding header columns
   * @param text
   *          text to be set
   */
  public void setCellText(int dataRowIdx, int dataColIdx, String text) {
    dataFT.setText(getTblRowIdx(dataRowIdx), getTblColIdx(dataColIdx), text);
  }

  /**
   * Sets widget in a table cell, called from row populate callback
   * 
   * @param dataRowIdx
   *          row index excluding header rows
   * @param dataColIdx
   *          column index excluding header columns
   * @param text
   *          widget to be set
   */
  public void setCellWidget(int dataRowIdx, int dataColIdx, Widget widget) {
    dataFT
        .setWidget(getTblRowIdx(dataRowIdx), getTblColIdx(dataColIdx), widget);
  }

  /**
   * Gets widget in a table cell
   * 
   * @param dataRowIdx
   *          row index excluding header rows
   * @param dataColIdx
   *          column index excluding header columns
   */
  public Widget getCellWidget(int dataRowIdx, int dataColIdx) {
    return dataFT.getWidget(getTblRowIdx(dataRowIdx), getTblColIdx(dataColIdx));
  }

  /**
   * clear a table cell (this isn't working always; use setCellWidget instead)
   * 
   * @param dataRowIdx
   *          row index excluding header rows
   * @param dataColIdx
   *          column index excluding header columns
   * @return
   */
  public boolean clearCell(int dataRowIdx, int dataColIdx) {
    return dataFT.clearCell(getTblRowIdx(dataRowIdx), getTblColIdx(dataColIdx));
  }

  /**
   * Called with parameter 'true' at the start of an action that may cause row
   * re-arrangement, e.g. row delete, by the DataTable instance. Called with
   * parameter 'false' at the end of the action by the callback method.
   */
  public void setRowActionInProgress(boolean rowActionInProgress) {
    this.rowActionInProgress = rowActionInProgress;
  }

  private int getClickedTblRowIdx(ClickEvent event, int tblColIdx) {
    Widget clickedW = (Widget) event.getSource();
    int tblRowIdx;
    for (tblRowIdx = getTblColHdrRows(); tblRowIdx < dataFT.getRowCount(); tblRowIdx++) {
      Widget w = dataFT.getWidget(tblRowIdx, tblColIdx);
      if (w == clickedW)
        break;
    }
    return tblRowIdx;
  }

  private class TableColumn {
    private String name;
    private String width;

    private String getHeader() {
      return name;
    }

    private void setHeader(String header) {
      this.name = header;
    }

    private String getWidth() {
      return width;
    }

    private void setWidth(String width) {
      this.width = width;
    }
  }

  /**
   * Row actions supported by DataTable
   */
  public enum Action {
    Custom, PreCreate, Delete, Populate, PreEdit, PostEdit, Select, Detail
  }

  private class RowAction {
    private Action action;
    private ImageResource imageR;
    private String imageS;
    private String label;
    private RowActionClickHandler handler;

    private RowAction(Action action, String icon, String label) {
      this.action = action;
      this.imageS = icon;
      this.label = label;
      this.handler = new RowActionClickHandler(action);
    }

    private RowAction(Action action) {
      this.action = action;
      switch (action) {
      case PreEdit:
        label = "Edit Row";
        imageR = Resources.I.getInlineUpdate();
        break;
      case Detail:
        label = "Row Detail";
        imageR = Resources.I.getInlineRetrieve();
        break;
      case Delete:
        label = "Delete Row";
        imageR = Resources.I.getInlineDelete();
        break;
      case Select:
        label = "Select Row";
        imageR = Resources.I.getInlineRadio();
        break;
      }
      handler = new RowActionClickHandler(action);
    }
  }

  // number of columns and column index utilities

  private int getTblRowHdrCols() {
    return enableMultiSelect ? 1 : 0;
  }

  private int getDataCols() {
    return columns.size();
  }

  private int getTblColIdx(int dataColIdx) {
    return getTblRowHdrCols() + dataColIdx;
  }

  private int getDataColIdx(int tblColIdx) {
    return tblColIdx - getTblRowHdrCols();
  }

  private int getTblActionColIdx(Action action) {
    if (action == Action.PostEdit)
      action = Action.PreEdit;
    int i = 0;
    for (; i < rowActions.size(); i++)
      if (rowActions.get(i).action == action)
        break;
    return getTblRowHdrCols() + getDataCols() + i;
  }

  // number of rows and row index utilities

  private int tblColHdrRows = -1;

  private int getTblColHdrRows() {
    if (tblColHdrRows != -1)
      return tblColHdrRows;
    if (columns == null)
      return 0;
    for (TableColumn column : columns) {
      if (column.getHeader() != null) {
        tblColHdrRows = 1;
        return 1;
      }
    }
    tblColHdrRows = 0;
    return 0;
  }

  private int getTblRowIdx(int dataRowIdx) {
    return getTblColHdrRows() + dataRowIdx;
  }

  private int getDataRowIdx(int tblRowIdx) {
    return tblRowIdx - getTblColHdrRows();
  }

  private int getObjIdx(int dataRowIdx) {
    return pageStartIndex + dataRowIdx;
  }
}
